﻿using System;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Threading;
using Microsoft.SharePoint;
using System.Data;
using System.Security.Principal;
using System.Security.Permissions;
using System.Data.SqlClient;
using System.Runtime.InteropServices;

#pragma warning disable 3019

//This class is a collection of utilities that bypass or enhance the
//SharePoint api in ways that are not supported by MS, for example
//we disable event firing for the current thread, or read/write directly
//to content columns. Most of this was figured out by snooping around in 
//the SQL stored procs, or peeking at the assembly code using .NET Reflector
//the method names are pretty self explanatory 
namespace Version3.SharePoint
{



    [Guid("17023D5B-460C-40b7-ACB6-85E6A3CE2343")]
    public class UtilityMethods
    {
        #region Declarations
        //Ideally these should be stored procedures but thats probably going
        //to far,seeing as how we are in the middle of the WSS content database
    
        private const string SQL_FIND_NTEXT_POSITION = "SELECT  @OffSet = PATINDEX('%%SMALLTEXTSTRING%%',%COLNAME%) FROM AllUserData WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_DELETE_NTEXT_POSITION = "UPDATETEXT AllUserData.%COLNAME% @Pointer @OffSet %STRLEN%";
        private const string SQL_READ_NTEXT = "SELECT %COLNAME% FROM AllUserData WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const string SQL_UPDATE_NTEXT = "UPDATETEXT AllUserData.%COLNAME% @Pointer NULL 0 '%SMALLTEXTSTRING%'";
        private const string SQL_GET_NTEXT_PTR = "SELECT @Pointer=TEXTPTR( %COLNAME%) FROM AllUserData WHERE tp_ListId = '%LISTID%' AND tp_ID = %ITEMID%";
        private const int SQL_READ_NTEXT_BUFFER_SIZE = 1024;

        #endregion

        #region RemoveTextFromSharePointNTEXTUserDataColumn
        public static void RemoveTextFromSharePointNTEXTUserDataColumn(string textToRemove, Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            RemoveTextFromSharePointNTEXTUserDataColumn(textToRemove, siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static void RemoveTextFromSharePointNTEXTUserDataColumn(string textToRemove, Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            if (textToRemove.Length > 512 || -1 != textToRemove.IndexOf("'") || string.IsNullOrEmpty(textToRemove))
            {
                throw new ArgumentException("Invald textToRemove value or format");
            }
            using (SPSite currentSite = new SPSite(siteId))
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                    StringBuilder sqlCommandText = new StringBuilder();
                    sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                    sqlCommandText.AppendLine("BEGIN TRANSACTION");
                    sqlCommandText.AppendLine("DECLARE @Pointer binary(16)");
                    sqlCommandText.AppendLine("DECLARE @OffSet INT");
                    sqlCommandText.AppendLine("SET @OffSet = 0");

                    string pointerQuery = SQL_GET_NTEXT_PTR.Replace("%COLNAME%", ntextColumnName).Replace("%ITEMID%", itemId.ToString());
                    pointerQuery = pointerQuery.Replace("%LISTID%", listId.ToString().ToUpper());
                    sqlCommandText.AppendLine(pointerQuery);

                    string findPositionQuery = SQL_FIND_NTEXT_POSITION.Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", textToRemove);
                    findPositionQuery = findPositionQuery.Replace("%LISTID%", listId.ToString().ToUpper()).Replace("%ITEMID%", itemId.ToString());
                    sqlCommandText.AppendLine(findPositionQuery);

                    sqlCommandText.AppendLine("If @OffSet > 0");
                    sqlCommandText.AppendLine("BEGIN");
                    int removeLen = textToRemove.Length - 1;

                    sqlCommandText.AppendLine(SQL_DELETE_NTEXT_POSITION.Replace("%COLNAME%", ntextColumnName).Replace("%STRLEN%", removeLen.ToString()));
                    sqlCommandText.AppendLine("END");

                    sqlCommandText.AppendLine("COMMIT TRANSACTION");
                    string commandText = sqlCommandText.ToString();
                    using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                    {
                        sqlConn.Open();
                        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                        {
                            sqlCommand.ExecuteNonQuery();
                        }
                    }
                });
            }
        }
        #endregion

        #region AppendTextToSharePointNTEXTUserDataColumn
        public static void AppendTextToSharePointNTEXTUserDataColumn(string textToAppend, Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            AppendTextToSharePointNTEXTUserDataColumn(textToAppend, siteId, listId, itemId, columnNameAttribute.InnerText);
        }

        public static void AppendTextToSharePointNTEXTUserDataColumn(string textToAppend, Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            if (textToAppend.Length > 512 || -1 != textToAppend.IndexOf("'"))
            {
                throw new ArgumentException("Invald textToAppend value or format");
            }
            using (SPSite currentSite = new SPSite(siteId))
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                 {
                     string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                     StringBuilder sqlCommandText = new StringBuilder();
                     sqlCommandText.AppendLine("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
                     sqlCommandText.AppendLine("BEGIN TRANSACTION");
                     sqlCommandText.AppendLine("DECLARE @Pointer binary(16)");
                     string pointerQuery = SQL_GET_NTEXT_PTR.Replace("%COLNAME%", ntextColumnName).Replace("%ITEMID%", itemId.ToString());
                     pointerQuery = pointerQuery.Replace("%LISTID%", listId.ToString().ToUpper());
                     sqlCommandText.AppendLine(pointerQuery);
                     sqlCommandText.AppendLine(SQL_UPDATE_NTEXT.Replace("%COLNAME%", ntextColumnName).Replace("%SMALLTEXTSTRING%", textToAppend));
                     sqlCommandText.AppendLine("COMMIT TRANSACTION");
                     string commandText = sqlCommandText.ToString();
                     using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                     {
                         sqlConn.Open();
                         using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                         {
                             sqlCommand.ExecuteNonQuery();
                         }
                     }
                 });
            }
        }
        #endregion

        #region ReadNTEXTDirectlyFromSharePointUserDataColumn
        public static String ReadNTEXTDirectlyFromSharePointUserDataColumn(Guid siteId, Guid listId, int itemId, SPField sharePointField)
        {
            XmlDocument schemaXML = new XmlDocument();
            schemaXML.LoadXml(sharePointField.SchemaXml);
            XmlNode fieldNode = schemaXML.SelectSingleNode("/Field");
            XmlAttribute columnNameAttribute = fieldNode.Attributes["ColName"];
            return ReadNTEXTDirectlyFromSharePointUserDataColumn(siteId,listId, itemId, columnNameAttribute.InnerText);
        }

        public static String ReadNTEXTDirectlyFromSharePointUserDataColumn(Guid siteId, Guid listId, int itemId, string ntextColumnName)
        {
            StringBuilder returnValue = new StringBuilder();
            using (SPSite currentSite = new SPSite(siteId))
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string dbConnectionString = currentSite.ContentDatabase.DatabaseConnectionString;
                    //this is not the technically correct way to read a large nText column however
                    //it will pull back however it works fine and still pages correctly
                    string commandText = SQL_READ_NTEXT.Replace("%COLNAME%", ntextColumnName).Replace("%ITEMID%", itemId.ToString());
                    commandText = commandText.Replace("%LISTID%", listId.ToString().ToUpper());
                    using (SqlConnection sqlConn = new SqlConnection(dbConnectionString))
                    {
                        sqlConn.Open();
                        using (SqlCommand sqlCommand = new SqlCommand(commandText, sqlConn))
                        {
                            SqlDataReader sqlReader = sqlCommand.ExecuteReader();
                            if (sqlReader.Read())
                            {
                                Char[] charBuffer = new Char[SQL_READ_NTEXT_BUFFER_SIZE];
                                int startIndex = 0;
                                int bytesRead = (int)sqlReader.GetChars(0, startIndex, charBuffer, 0, SQL_READ_NTEXT_BUFFER_SIZE);
                                while (SQL_READ_NTEXT_BUFFER_SIZE == bytesRead)
                                {
                                    returnValue.Append(charBuffer, 0, bytesRead);
                                    startIndex += SQL_READ_NTEXT_BUFFER_SIZE;
                                    bytesRead = (int)sqlReader.GetChars(0, startIndex, charBuffer, 0, SQL_READ_NTEXT_BUFFER_SIZE);
                                }
                                if (bytesRead > 0) returnValue.Append(charBuffer, 0, bytesRead);
                            }
                        }
                    }
                });

            }
            return returnValue.ToString();
        }
        #endregion

        #region Folder and thread query routines

        public static SPListItemCollection FindAllItemPeersInFolder(SPListItem itemInFolder)
        {
            return FindAllItemPeersInFolder(itemInFolder, null);
        }

        public static SPListItemCollection FindAllItemPeersInFolder(SPListItem itemInFolder, string viewFields)
        {
            //If the item is a folder then return its contents
            if (null != itemInFolder.Folder)
            {
                return FindAllItemsInFolder(itemInFolder.Folder, itemInFolder.ParentList, viewFields);
            }
            else
            {
                //Build a query for all non folder items with the same FileDirRef
                SPListItemCollection returnValue = null;
                try
                {

                    string fileDirRef = (string)itemInFolder["FileDirRef"];
                    if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><Neq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Neq>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef);
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");
                    SPQuery folderQuery = new SPQuery();
                    folderQuery.Query = queryXML.ToString();
                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    if (!string.IsNullOrEmpty(viewFields))
                    {
                        folderQuery.ViewFields = viewFields;
                    }
                    returnValue = itemInFolder.ParentList.GetItems(folderQuery);
                    if (returnValue.Count < 1) returnValue = null;
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                return returnValue;

            }
        }

        public static SPListItemCollection FindAllItemsInFolder(SPFolder folderToSearch, SPList listToSearch)
        {
            return FindAllItemsInFolder(folderToSearch, listToSearch, null);
        }

        public static SPListItemCollection FindAllItemsInFolder(SPFolder folderToSearch, SPList listToSearch, string viewFields)
        {
            SPListItemCollection returnValue = null;
            try
            {
                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = "<Where><Neq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Neq></Where>";
                if (null != folderToSearch)
                {
                    folderQuery.Folder = folderToSearch;
                }
                if (!string.IsNullOrEmpty(viewFields))
                {
                    folderQuery.ViewFields = viewFields;
                }
                returnValue = listToSearch.GetItems(folderQuery);

                if (returnValue.Count < 1) returnValue = null;
            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                returnValue = null;
            }
            return returnValue;
        }

        public static SPListItemCollection FindAllItemsInThread(SPListItem threadMember)
        {
            return FindAllItemsInThread(threadMember, null);
        }

        public static SPListItemCollection FindAllItemsInThread(SPListItem threadMember, string viewFields)
        {
            SPListItemCollection returnValue = null;
            try
            {
                if (threadMember.Fields.ContainsField("ParentFolderId"))
                {
                    Int32 parentFolderID = (Int32)threadMember["ParentFolderId"];
                    SPFolder discussionFolder = FindFolderInList(parentFolderID, threadMember.ParentList);
                    returnValue = FindAllItemsInFolder(discussionFolder, threadMember.ParentList, viewFields);
                }
            }
            catch (Exception likelyCastException)
            {
                System.Diagnostics.Trace.WriteLine(likelyCastException.Message);
                returnValue = null;
            }
            if (null == returnValue)
            {
                returnValue = FindAllItemPeersInFolder(threadMember, viewFields);
            }

            return returnValue;
        }

        public static SPFolder ParentFolder(SPListItem childItem)
        {
            string fileDirRef = (string)childItem["FileDirRef"];
            return FindFolderInList(fileDirRef, childItem.ParentList);
        }

        public static SPFolder FindFolderInList(string fileDirRef, SPList folderList)
        {
            SPFolder returnValue = null;
            if (ListSupportsFolders(folderList))
            {
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                if (fileDirRef.StartsWith("/")) fileDirRef = fileDirRef.Substring(1);
                try
                {
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><And><Eq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Eq>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileLeafRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef.Substring(fileDirRef.LastIndexOf('/') + 1));
                    queryXML.Append("</Value></Eq></And>");
                    queryXML.Append("<Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                    queryXML.Append(fileDirRef.Substring(0, fileDirRef.LastIndexOf('/')));
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");
                    SPQuery folderQuery = new SPQuery();
                    folderQuery.Query = queryXML.ToString();
                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    SPListItemCollection foundItems = folderList.GetItems(folderQuery);
                    if (foundItems.Count > 0)
                    {
                        returnValue = foundItems[0].Folder;
                    }
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                if (null == returnValue)
                {
                    //This is a hold over for when the schema is customized and the content type of the folder
                    //works for the API but is not of content type Folder
                    foreach (SPListItem folderItem in folderList.Folders)
                    {
                        string fileRef = (string)folderItem["FileRef"];
                        if (-1 != fileRef.IndexOf(";#")) fileRef = fileRef.Substring(fileRef.IndexOf(";#") + 2);
                        if (0 == fileRef.IndexOf('/')) fileRef = fileRef.Substring(1);
                        if (0 == string.Compare(fileRef, fileDirRef, StringComparison.InvariantCultureIgnoreCase))
                        {
                            returnValue = folderItem.Folder;
                            break;
                        }
                    }
                }
            }
            return returnValue;
        }

        public static SPFolder FindFolderInList(Int32 folderId, SPList folderList)
        {
            SPFolder returnValue = null;
            if (ListSupportsFolders(folderList))
            {
                try
                {
                    System.Text.StringBuilder queryXML = new StringBuilder();
                    queryXML.Append("<Where><And><Eq><FieldRef Name='ContentType' /><Value Type='Text'>Folder</Value></Eq>");
                    queryXML.Append("<Eq><FieldRef Name=\"ID\" /><Value Type=\"Counter\">");
                    queryXML.Append(folderId.ToString(System.Globalization.CultureInfo.InvariantCulture));
                    queryXML.Append("</Value></Eq>");
                    queryXML.Append("</And></Where>");

                    SPQuery folderQuery = new SPQuery();
                    folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                    SPListItemCollection foundItems = folderList.GetItems(folderQuery);
                    if (foundItems.Count > 0)
                    {
                        returnValue = foundItems[0].Folder;
                    }
                }
                catch (Exception queryError)
                {
                    System.Diagnostics.Trace.WriteLine(queryError.Message);
                    returnValue = null;
                }
                //try it the slow way, 
                if (null == returnValue)
                {
                    foreach (SPListItem folderItem in folderList.Folders)
                    {
                        if (folderId == folderItem.ID)
                        {
                            returnValue = folderItem.Folder;
                            break;
                        }
                    }
                }
            }

            return returnValue;
        }

        public static bool ListSupportsFolders(SPList listToEvaluate)
        {
            if (listToEvaluate.ServerTemplateCanCreateFolders) return true;
            if (listToEvaluate.BaseTemplate == SPListTemplateType.DiscussionBoard) return true;
            return false;
        }
        #endregion

        #region SPQuery methods

        public static int CountOfPeerItemsAndSubFolders(SPListItem itemInFolder)
        {
            try
            {
               
                string fileDirRef = (string)itemInFolder["FileDirRef"];
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                System.Text.StringBuilder queryXML = new StringBuilder();
                queryXML.Append("<Where><Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                queryXML.Append(fileDirRef);
                queryXML.Append("</Value></Eq></Where>");
                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = queryXML.ToString();
                folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                folderQuery.ViewFields = "<FieldRef Name=\"ID\"/>" ;
                SPListItemCollection itemCollection = itemInFolder.ParentList.GetItems(folderQuery);
                return itemCollection.Count;
            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                return 0;
            }
        }

        public static int CountOfPeerReadItemsAndSubFolders(SPListItem itemInFolder, string fieldName, int userId)
        {
            try
            {
                string readMark = ReadUnreadMarksDataField.READ_MARK_DELIMITER + userId.ToString() + ReadUnreadMarksDataField.READ_MARK_DELIMITER;
                string fileDirRef = (string)itemInFolder["FileDirRef"];
                if (-1 != fileDirRef.IndexOf(";#")) fileDirRef = fileDirRef.Substring(fileDirRef.IndexOf(";#") + 2);
                System.Text.StringBuilder queryXML = new StringBuilder();
                queryXML.Append("<Where><And><Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Lookup\">");
                queryXML.Append(fileDirRef);
                queryXML.Append("</Value></Eq><Contains><FieldRef Name=\"");
                queryXML.Append(fieldName);
                queryXML.Append("\" /><Value Type=\"Text\">");
                queryXML.Append(readMark);
                queryXML.Append("</Value></Contains></And></Where>");

                SPQuery folderQuery = new SPQuery();
                folderQuery.Query = queryXML.ToString();
                folderQuery.ViewAttributes = "Scope=\"RecursiveAll\"  ModerationType=\"Moderator\"";
                folderQuery.ViewFields = "<FieldRef Name=\"ID\"/>";
                SPListItemCollection itemCollection = itemInFolder.ParentList.GetItems(folderQuery);
                return itemCollection.Count;
            }
            catch (Exception queryError)
            {
                System.Diagnostics.Trace.WriteLine(queryError.Message);
                return 0;
            }
        }

        #endregion

        #region Save item

        //This is a simple wrapper around SPItemEventReceiver
        //that lets us call into the event receiver event manager
        //which in turn calls some functions that manipulate
        //thread local storage to set flags for this thread
        //about the state of event propagation
        internal class EventBypass : SPItemEventReceiver
        {
            //Since the SharePoint event manager uses ThreadLocalStorage for this
            //flag, I am a bit concerned about using a "global static" but for now
            //it works, probably should be load tested.
            private bool _EventFiring = true;
            public bool EventFiring
            {
                get
                {
                    return _EventFiring;
                }

                set
                {
                    if (value != _EventFiring)
                    {
                        if (value)
                        {
                            base.EnableEventFiring();
                            _EventFiring = true;
                        }
                        else
                        {
                            base.DisableEventFiring();
                            _EventFiring = false;
                        }
                    }
                }
            }
        }
        private static EventBypass g_EventManager = new EventBypass();

        public static void WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(ReadUnreadMarksDataFieldValue fieldValue, string fieldName, Guid itemId, Guid listId, Guid webId, Guid siteId)
        {
            //If we dont have permission to write the item (e.g. we are a reader)
            //then we need to do the update in an elevated security context
            //We dont want to trigger any events, synchronous or otherwise so
            //we use the SaveSPListItemWithoutFiringEvents wrapper to
            //save the item without notifying the event queues.
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite currentSite = new SPSite(siteId))
                {
                    using (SPWeb currentWeb = currentSite.AllWebs[webId])
                    {
                        bool currentUnsafeSetting = currentWeb.AllowUnsafeUpdates;
                        currentWeb.AllowUnsafeUpdates = true;
                        SPList currentList = currentWeb.Lists[listId];
                        SPListItem currentItem = currentList.GetItemByUniqueId(itemId);
                        currentItem[fieldName] = fieldValue;
                        SaveSPListItemWithoutFiringEvents(currentItem, true);
                        currentWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                    }
                }
            });
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave)
        {
            SaveSPListItemWithoutFiringEvents(itemToSave, false, false);
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave, bool systemUpdate)
        {
            SaveSPListItemWithoutFiringEvents(itemToSave, false, true);
        }

        public static void SaveSPListItemWithoutFiringEvents(SPListItem itemToSave, bool systemUpdate, bool incrementListItemVersion)
        {
            //As it turns out, the DisableEventFiring and EnableEventFiring functions in
            //an event handler call a static global manager that uses thread local storage
            //to set flags that the unmanaged codebase recognizes. So a simple event base wrapper 
            //lets us access the static functions that manage event control for the current thread.
            g_EventManager.EventFiring = false;
            try
            {
                if (systemUpdate)
                {
                    itemToSave.SystemUpdate(incrementListItemVersion);
                }
                else
                {
                    itemToSave.SystemUpdate();
                }
            }
            finally
            {
                g_EventManager.EventFiring = true;

            }
        }
        #endregion

        #region Read marking
        public static void MarkItemAsRead(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField)
        {
            if (null != listItem.Folder)
            {
                MarkFolderSubTreeAsRead(currentUser, listItem.Folder, readUnreadField);
            }
            MarkItemAsRead(currentUser, listItem, readUnreadField, true);
        }

        private static void MarkFolderSubTreeAsRead(SPUser currentUser, SPFolder topFolder, ReadUnreadMarksDataField readUnreadField)
        {
            //if we are a folder tree then collect all of the child items and mark them as read
            string viewFieldXML = string.Empty;
            if (!readUnreadField.WriteDirect) viewFieldXML = "<FieldRef Name=\"" + readUnreadField.InternalName + "\"/>";
            SPListItemCollection childItems = FindAllItemsInFolder(topFolder, topFolder.Item.ParentList, viewFieldXML);
            if (null != childItems)
            {
                foreach (SPListItem childItem in childItems)
                {
                    MarkItemAsRead(currentUser, childItem, readUnreadField, false);
                }
            }
            if (null != topFolder.SubFolders)
            {
                //dont forget to walk down the tree
                foreach (SPFolder childFolder in topFolder.SubFolders)
                {
                    MarkFolderSubTreeAsRead(currentUser, childFolder, readUnreadField);
                }
            }
            MarkItemAsRead(currentUser, topFolder.Item, readUnreadField, false);
        }

        private static void MarkParentFolderAsRead(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField)
        {
            //find the parent folder, if it exists and mark it
            SPFolder parentFolder = UtilityMethods.ParentFolder(listItem);
            if (null != parentFolder)
            {
                MarkItemAsRead(currentUser, parentFolder.Item, readUnreadField);
            }
        }


        private static void MarkItemAsRead(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField, bool checkHierarchy)
        {
            //collect the value 
            ReadUnreadMarksDataFieldValue readMarksValue = null;
            try
            {
                readMarksValue = (ReadUnreadMarksDataFieldValue)listItem[readUnreadField.InternalName];
            }
            catch
            {
                readMarksValue = null;
            }
            if (null == readMarksValue) readMarksValue = new ReadUnreadMarksDataFieldValue();
            //Depending on the configuration options we will either write directly to the column
            //or call our wrapped item update function
            if (readUnreadField.WriteDirect)
            {
                string readMark = currentUser.ID.ToString() + ReadUnreadMarksDataField.READ_MARK_DELIMITER;
                if (readMarksValue.IsEmpty)
                {
                    readMark = ReadUnreadMarksDataField.READ_MARK_DELIMITER + currentUser.ID.ToString() + ReadUnreadMarksDataField.READ_MARK_DELIMITER;
                }
                AppendTextToSharePointNTEXTUserDataColumn(readMark,
                                                          listItem.ParentList.ParentWeb.Site.ID,
                                                          listItem.ParentList.ID,
                                                          listItem.ID,
                                                          readUnreadField);

            }
            else
            {


                readMarksValue.MarkUserRead(currentUser.ID);
                WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(readMarksValue,
                                                                                             readUnreadField.InternalName,
                                                                                             listItem.UniqueId,
                                                                                             listItem.ParentList.ID,
                                                                                             listItem.ParentList.ParentWeb.ID,
                                                                                             listItem.ParentList.ParentWeb.Site.ID);


            }

            if (checkHierarchy)
            {
                //only process the parent tree if the event
                //handler is also wired up
 
                if (ListSupportsFolders(listItem.ParentList))
                {
                    //If we are in a folder capable list, then we want to find
                    //all the items in the current folder and if they are all
                    //read, then we want to mark the parent folder as read as well
                    //since sharepoint does not have a "Not Contains" query we
                    //will determine the status by checking the count of read
                    //items against the count of actual items. Since the count
                    //needs to include sub folders we cant use the built in collections
                    int readCount = CountOfPeerReadItemsAndSubFolders(listItem,
                                                                     readUnreadField.InternalName,
                                                                     currentUser.ID);
                    int totalCount = CountOfPeerItemsAndSubFolders(listItem);

                    if (totalCount == readCount)
                    {
                        MarkParentFolderAsRead(currentUser, listItem, readUnreadField);
                    }
                }
                
            }
        }
        #endregion

        #region Unread marking

        public static void MarkItemAsUnread(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField)
        {
            //In our public method, if the first requested item is a folder
            //we will mark all of its children as unread as well
            if (null != listItem.Folder)
            {
                MarkFolderSubTreeAsUnread(currentUser, listItem.Folder, readUnreadField);
            }
            MarkItemAsUnread(currentUser, listItem, readUnreadField, true);
        }

        private static void MarkFolderSubTreeAsUnread(SPUser currentUser, SPFolder topFolder, ReadUnreadMarksDataField readUnreadField)
        {
            //if we are a folder tree then collect all of the child items and mark them as read
            string viewFieldXML = string.Empty;
            if(!readUnreadField.WriteDirect) viewFieldXML = "<FieldRef Name=\"" + readUnreadField.InternalName + "\"/>";
            SPListItemCollection childItems = FindAllItemsInFolder(topFolder, topFolder.Item.ParentList, viewFieldXML);
            if (null != childItems)
            {
                foreach (SPListItem childItem in childItems)
                {
                    MarkItemAsUnread(currentUser, childItem, readUnreadField, false);
                }
            }
            if (null != topFolder.SubFolders)
            {
                //dont forget to walk down the tree
                foreach (SPFolder childFolder in topFolder.SubFolders)
                {
                    MarkFolderSubTreeAsUnread(currentUser, childFolder, readUnreadField);
                    MarkItemAsUnread(currentUser, childFolder.Item, readUnreadField, false);
                }
            }
        }

        private static void MarkParentFolderAsUnread(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField)
        {
            //find the parent folder, if it exists and mark it
            SPFolder parentFolder = UtilityMethods.ParentFolder(listItem);
            if (null != parentFolder)
            {
                MarkItemAsUnread(currentUser, parentFolder.Item, readUnreadField, true);
            }
        }

        private static void MarkItemAsUnread(SPUser currentUser, SPListItem listItem, ReadUnreadMarksDataField readUnreadField, bool checkHierarchy)
        {
            //Similar routine to mark as read, except reversing
            //the decision. The event handler has its own routines
            //because it actually clears the read marks for all but
            //the editing user.
            if (readUnreadField.WriteDirect)
            {
                string readMark = ReadUnreadMarksDataField.READ_MARK_DELIMITER + currentUser.ID.ToString() + ReadUnreadMarksDataField.READ_MARK_DELIMITER;
                UtilityMethods.RemoveTextFromSharePointNTEXTUserDataColumn(readMark,
                                                                           listItem.ParentList.ParentWeb.Site.ID, 
                                                                           listItem.ParentList.ID,
                                                                           listItem.ID, 
                                                                           readUnreadField);

            }
            else
            {
                ReadUnreadMarksDataFieldValue readMarksValue = (ReadUnreadMarksDataFieldValue)listItem[readUnreadField.InternalName];
                if (null == readMarksValue) readMarksValue = new ReadUnreadMarksDataFieldValue();
                readMarksValue.MarkUserUnread(currentUser.ID);
                WriteReadUnreadMarkValueFieldToSPListItemIgnoringSecurityWithoutFiringEvents(readMarksValue,
                                                                                             readUnreadField.InternalName,
                                                                                             listItem.UniqueId,
                                                                                             listItem.ParentList.ID,
                                                                                             listItem.ParentList.ParentWeb.ID,
                                                                                             listItem.ParentList.ParentWeb.Site.ID);
            }

            if (checkHierarchy)
            {

                if (ListSupportsFolders(listItem.ParentList))
                {
                    MarkParentFolderAsUnread(currentUser, listItem, readUnreadField);
                }
            }
        }

        #endregion


        #region Ensure User
        static internal SPUser EnsureUser(string loginName, SPWeb userWeb)
        {
            SPUser returnValue = null;
            try
            {
                returnValue = userWeb.AllUsers[loginName];
            }
            catch
            {
                returnValue = null;
            }
            if (null == returnValue)
            {
                Guid siteID = userWeb.Site.ID;
                Guid webId = userWeb.ID;
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite currentSite = new SPSite(siteID))
                    {
                        using (SPWeb currentWeb = currentSite.AllWebs[webId])
                        {
                            //allow unsafe updates
                            bool currentUnsafeSetting = currentWeb.AllowUnsafeUpdates;
                            currentWeb.AllowUnsafeUpdates = true;
                            returnValue = currentWeb.EnsureUser(loginName);
                            currentWeb.Update();
                            currentWeb.AllowUnsafeUpdates = currentUnsafeSetting;
                        }
                    }
                });

            }
            return returnValue;
        }

        static internal SPUser EnsureUser(string loginName)
        {
            return EnsureUser(loginName, SPContext.Current.Web);
        }
        #endregion
    }
}
